package taller2.view.menus;

import java.awt.Font;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;

import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;

import taller2.interfaces.AlgorithmApplicator;
import taller2.interfaces.Graph;
import taller2.utils.Constants;
import taller2.utils.Utils;
import taller2.view.components.CharacterBox;
import taller2.view.components.MyHighlighter;
import taller2.view.windows.MyWindow;


public class CodeBrowser extends JPanel{
        private static final long serialVersionUID = 1L;
       
        private AlgorithmApplicator algorithmApplicator;
        private Graph graph;
        private MyWindow window;
        
        private String code;
        private JScrollPane scrollBar;
        private int fontSize;
        private ArrayList<Integer> previousScrolls;
        private CharacterBox textoExtra;

        private Thread threadAutoscroller;
        private AutoScroller autoscroller;

        private JTextArea textArea;
        private MyHighlighter highlighter;
        private long scrollTime;
        
        // constructor
        public CodeBrowser (String codeAddr,int font, AlgorithmApplicator algorithmApplicator, MyWindow window, Graph graph, boolean textoExtra){
        	
        		readFile(codeAddr, Charset.defaultCharset());
                this.fontSize = font;
                
                if (textoExtra){
                	this.textoExtra = new CharacterBox(algorithmApplicator, window);
                }
                
                createComponents();
               
                this.previousScrolls = new ArrayList<Integer>();
                this.previousScrolls.add(0);

                this.graph = graph;
                this.algorithmApplicator = algorithmApplicator;
                this.window = window;
       
                this.scrollTime = Constants.StartingScrollTime;
        }
       
        // converts a file into a string
        private void readFile(String path, Charset encoding){
                byte[] encoded = null;
                try {
                        encoded = Files.readAllBytes(Paths.get(path));
                } catch (IOException e) {
                        e.printStackTrace();
                }
                code = new String(encoded, encoding);
        }
       
        // converts a file into a string and loads it to the area
        // resets the scrollbar and highlight counter
        public void changeCode(String path){
                readFile(path,Charset.defaultCharset());
                textArea.setText(code);
                resetPosition();
        }
       
        // scrolls to the beginning of the file and unhighlights the text
        private void resetPosition(){
        	highlighter.reset();
            scrollBar.getViewport().setViewPosition(new java.awt.Point(0,0));              
        }
       
        // creates and configures the textArea, the scrollbar and the objects used for highlighting
        private void createComponents(){
                textArea = new JTextArea(code);
                textArea.setMargin(new Insets(0,0,0,0));
                textArea.setEditable(false);
                textArea.setFont(new Font("Serif",Font.PLAIN,(int)(fontSize*1.5)));
               
                highlighter = new MyHighlighter(textArea);
               
                scrollBar = new JScrollPane(textArea);
                scrollBar.setHorizontalScrollBarPolicy( JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
                scrollBar.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);                  
                
                setLayout(new GridBagLayout());
                if (textoExtra != null){
                    Utils.addComponent(this,scrollBar,0.2,0.8,new taller2.utils.Point(0,0));
                    Utils.addComponent(this,textoExtra,0.2,0.2,new taller2.utils.Point(0,1));
                }else{
                    Utils.addComponent(this,scrollBar,0.2,1,new taller2.utils.Point(0,0));
                }


        }
              
        // highlights the next line indicated 
        // and replaces the graph indicated by 
        // the algorithm applicator 
        //
        // if the algorithm applicator is null
        // it only advances 1 line in the code
        // without changing the graph
        public String stepForward(){
        	highlighter.unHighlight();
        	
        	String line;        	
        	if(algorithmApplicator == null){
        		line = highlightNextLine();
        	}else{
                algorithmApplicator.NextStep();
    			if (this.textoExtra != null) textoExtra.update();
                        		
        		int posHighlight = algorithmApplicator.getPseudoCodeLine();
        		highlighter.highlightLine(posHighlight);
        		
        		line = highlighter.getLine(code,posHighlight);
                
        		String path = algorithmApplicator.getGraphPath();
        		if (path != null)
        			graph.load(path);               
        	}
        	window.redraw();
        	return line;        
        }

        // highlights the first line 
        // and replaces the graph indicated by 
        // the algorithm applicator 
        public String reset(){
        	highlighter.unHighlight();
        	
        	String line;        	
        	if(algorithmApplicator == null){
        		highlighter.highlightLine(0);
			line = highlightPrevLine();
        	}else{
                	algorithmApplicator.firstStep();
    			if (this.textoExtra != null) textoExtra.update();
                        		
        		int posHighlight = algorithmApplicator.getPseudoCodeLine();
        		highlighter.highlightLine(posHighlight);
        		
        		line = highlighter.getLine(code,posHighlight);
                
        		String path = algorithmApplicator.getGraphPath();
        		if (path != null)
        			graph.load(path);               
        	}
        	window.redraw();
        	return line;        
        }

        // highlights the previous line indicated 
        // and replaces the graph indicated by 
        // the algorithm applicator 
        //
        // if the algorithm applicator is null
        // it only goes back 1 line in the code
        // without changing the graph
        public String stepBackwards(){
        	highlighter.unHighlight();
        	
        	String line;        	
        	if(algorithmApplicator == null){
        		line = highlightPrevLine();
        	}else{        		
        		algorithmApplicator.previousStep();
    			if (this.textoExtra != null) textoExtra.update();
        		
        		int posHighlight = algorithmApplicator.getPseudoCodeLine();
        		highlighter.highlightLine(posHighlight);
        	
        		line = highlighter.getLine(code,posHighlight);
                
        		String path = algorithmApplicator.getGraphPath();
        		if (path != null)
        			graph.load(path);               
        	}
        	window.redraw();
        	return line;
        }
        
        // highlights sequentially ignoring whitespaces and returns new highlighted line, autoscrolls text when necessary
        public String highlightNextLine(){
        	highlighter.unHighlight();
        	
        	String line = highlighter.highlightNextLine();
                       
            if(line.isEmpty())
                return stepForward();
                      
             return line;
        }

       
        // highlights sequentially ignoring whitespaces and returns new highlighted line, autoscrolls text when necessary
        public String highlightPrevLine() {
                highlighter.unHighlight();
               
                String line = highlighter.highlightPrevLine();
                
                if(line.isEmpty())
                    return stepBackwards();
                
                return line;
        }
       
        // stops scrolling automatically
        public void stopAutoscroll() {
                if ((threadAutoscroller != null) && (threadAutoscroller.isAlive())){
                        autoscroller.finish();
               
                        try {
                                threadAutoscroller.join();
                        } catch (InterruptedException e) {
                                e.printStackTrace();
                        }
                }
        }
       
        
    	public void setAutoscrollTime(long time) {
    		scrollTime = time;
    	}
    	
    	public void setGraph(Graph graph){
    		this.graph = graph;
    	}

	public boolean isAutoscrolling(){
		if(threadAutoscroller == null)
			return false;

		return threadAutoscroller.isAlive();
	}

       // starts to scroll automatically
       public void startAutoscroll() {
               if ((threadAutoscroller == null) || (!threadAutoscroller.isAlive())){
                       autoscroller = new AutoScroller();
                       threadAutoscroller = new Thread(autoscroller);
                       threadAutoscroller.start();
               }
       }

        private class AutoScroller implements Runnable {
                private boolean finished;
               
                // so that the thread ends in the next run
                public void finish(){
                        finished = true;
                }
               
                // autoscrolls and sleeps regularly
                public void run() {
                        while(!finished){
                                stepForward();
                                int times = 0;
                                while(Constants.ScrollDelta*times < scrollTime){
                                        Utils.sleep(Constants.ScrollDelta*times);
                                        times++;
                                               
                                        if(finished)
                                                break;
                                }
                        }
                }
        }
       
}

